#include "https_connection.h"
#include <boost/beast/version.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/http.hpp>
#include <iostream>
#include <sstream>

HttpsConnection::HttpsConnection(boost::asio::io_context& io_context,
                                 boost::asio::ssl::context& ssl_context,
                                 const std::string& host,
                                 const std::string& port)
    : io_context_(io_context),
      ssl_context_(ssl_context),
      resolver_(io_context),
      ssl_stream_(io_context, ssl_context),
      host_(host),
      port_(port),
      connected_(false)
{
    ssl_context_.set_default_verify_paths();
    ssl_context_.set_verify_mode(boost::asio::ssl::verify_peer);
}

HttpsConnection::~HttpsConnection()
{
    closeConnection();
}

void HttpsConnection::connect()
{
    boost::system::error_code ec;
    auto const results = resolver_.resolve(host_, port_, ec);
    if (ec) throw std::runtime_error("Resolve failed: " + ec.message());

    boost::beast::get_lowest_layer(ssl_stream_).connect(results, ec);
    if (ec) throw std::runtime_error("Connect failed: " + ec.message());

    if (!SSL_set_tlsext_host_name(ssl_stream_.native_handle(), host_.c_str()))
        throw std::runtime_error("SNI set failed");

    ssl_stream_.handshake(boost::asio::ssl::stream_base::client, ec);
    if (ec) throw std::runtime_error("SSL handshake failed: " + ec.message());

    connected_ = true;
}

std::string HttpsConnection::sendRequest(const std::string& method,
                                         const std::string& target,
                                         const std::string& body,
                                         const std::vector<std::pair<std::string,std::string>>& headers)
{
    if (!connected_) connect();

    boost::beast::http::request<boost::beast::http::string_body> request;
    request.method(boost::beast::http::string_to_verb(method));
    request.target(target);
    request.version(11);
    request.set(boost::beast::http::field::host, host_);
    request.set(boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING);
    request.set(boost::beast::http::field::connection, "keep-alive");

    if (!body.empty())
    {
        request.body() = body;
        request.prepare_payload();
    }

    for (auto& h : headers)
        request.set(h.first, h.second);

    boost::system::error_code ec;
    boost::beast::http::write(ssl_stream_, request, ec);
    if (ec) throw std::runtime_error("HTTP write failed: " + ec.message());

    boost::beast::http::response<boost::beast::http::dynamic_body> response;
    boost::beast::http::read(ssl_stream_, buffer_, response, ec);
    if (ec) throw std::runtime_error("HTTP read failed: " + ec.message());

    std::ostringstream oss;
    oss << boost::beast::buffers_to_string(response.body().data());
    return oss.str();
}

void HttpsConnection::asyncSendRequest(const std::string& method,
                                       const std::string& target,
                                       const std::string& body,
                                       const std::vector<std::pair<std::string,std::string>>& headers,
                                       ResponseCallback callback)
{
    if (!connected_)
    {
        try
        {
            connect();
        }
        catch (const std::exception& e)
        {
            boost::asio::post(io_context_, [callback, e](){ callback(boost::beast::error_code(), e.what()); });
            return;
        }
    }

    auto request = std::make_shared<boost::beast::http::request<boost::beast::http::string_body>>();
    request->method(boost::beast::http::string_to_verb(method));
    request->target(target);
    request->version(11);
    request->set(boost::beast::http::field::host, host_);
    request->set(boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING);
    request->set(boost::beast::http::field::connection, "keep-alive");

    if (!body.empty())
    {
        request->body() = body;
        request->prepare_payload();
    }

    for (auto& h : headers)
        request->set(h.first, h.second);

    auto response = std::make_shared<boost::beast::http::response<boost::beast::http::dynamic_body>>();
    auto self = shared_from_this();

    boost::beast::http::async_write(ssl_stream_, *request,
        [self, request, response, callback](const boost::system::error_code& ec, std::size_t){
            if (ec)
            {
                callback(ec, "");
                return;
            }

            boost::beast::http::async_read(self->ssl_stream_, self->buffer_, *response,
                [response, callback](const boost::system::error_code& ec, std::size_t){
                    if (ec)
                    {
                        callback(ec, "");
                        return;
                    }
                    std::ostringstream oss;
                    oss << boost::beast::buffers_to_string(response->body().data());
                    callback(ec, oss.str());
                });
        });
}

void HttpsConnection::closeConnection()
{
    boost::system::error_code ec;
    if (ssl_stream_.lowest_layer().is_open())
        ssl_stream_.lowest_layer().close(ec);
    connected_ = false;
}
